﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics.Contracts;
using FoundationLib.Ifc;

namespace FoundationLib.Common.Collections
{
	/// <summary>
	/// Represents a paged list of objects.
	/// </summary>
	/// <typeparam name="T">The type of the list elements</typeparam>
	public class PagedList<T> : IEnumerable<T>
	{
		/// <summary>
		/// The paged list elements
		/// </summary>
		private IEnumerable<T> items;
		/// <summary>
		/// The total number of items
		/// </summary>
		private int totalItemCount;
		/// <summary>
		/// The 1-based page index
		/// </summary>
		private int pageIndex;
		/// <summary>
		/// The maximum number of elements per page
		/// </summary>
		private int pageSize;


		/// <summary>
		/// Creates a new <see cref="PagedList{T}"/> instance.
		/// </summary>
		/// <param name="pageItems">The paged items</param>
		/// <param name="pageIndex">The 1-based page index</param>
		/// <param name="pageSize">The maximum number of entries per page</param>
		/// <param name="totalItemCount">The total number of items available in the data source</param>
		public PagedList(IEnumerable<T> pageItems, int pageIndex, int pageSize, int totalItemCount)
		{
			Contract.Requires(pageItems != null);
			Contract.Requires(totalItemCount >= 0);
			Contract.Requires(pageIndex > 0);
			Contract.Requires(pageSize > 0);

			this.items = pageItems;
			this.pageIndex = pageIndex;
			this.pageSize = pageSize;
			this.totalItemCount = totalItemCount;
		}

		/// <summary>
		/// Creates a new <see cref="PagedList{T}"/> instance.
		/// </summary>
		/// <param name="pageItems">The paged items</param>
		/// <param name="page">The paging options</param>
		/// <param name="totalItemCount">The total number of items available in the data source</param>
		public PagedList(IEnumerable<T> pageItems, PagingOptions page, int totalItemCount)
		{
			Contract.Requires(pageItems != null);
			Contract.Requires(totalItemCount >= 0);
			Contract.Requires(page != null);

			this.items = pageItems;
			this.pageIndex = page.Index;
			this.pageSize = page.Size;
			this.totalItemCount = totalItemCount;
		}

		/// <summary>
		/// Gets the page items.
		/// </summary>
		public IEnumerable<T> Items
		{
			get
			{
				return this.items;
			}
		}

		/// <summary>
		/// Gets the first element index.
		/// </summary>
		public int StartIndex
		{
			get
			{
				return (((this.pageIndex - 1) * this.pageSize) + 1);
			}
		}

		/// <summary>
		/// Gets the last element index.
		/// </summary>
		public int EndIndex
		{
			get
			{
				return (this.totalItemCount > (this.pageIndex * this.pageSize) ? this.pageIndex * this.pageSize : this.totalItemCount);
			}
		}

		/// <summary>
		/// Gets the total number of pages.
		/// </summary>
		public int TotalPageCount
		{
			get
			{
				return (int)Math.Ceiling(this.totalItemCount / (double)this.pageSize);
			}
		}

		/// <summary>
		/// Gets the total number of items contained in the data source.
		/// </summary>
		public int TotalItemCount
		{
			get
			{
				return this.totalItemCount;
			}
		}

		/// <summary>
		/// Gets the current 1-based page index.
		/// </summary>
		public int PageIndex
		{
			get
			{
				return this.pageIndex;
			}
		}

		/// <summary>
		/// Gets the maximum number of entries per page.
		/// </summary>
		public int PageSize
		{
			get
			{
				return this.pageSize;
			}
		}

		/// <summary>
		/// Creates a new <see cref="PagedList{T}"/> from the given unpaged item list.
		/// </summary>
		/// <param name="unpagedItems">The unpaged data source</param>
		/// <param name="pageIndex">The 1-based page index</param>
		/// <param name="pageSize">The maximum number of entries per page</param>
		/// <returns>A <see cref="PagedList{T}"/> instance</returns>
		public static PagedList<T> Create(IEnumerable<T> unpagedItems, int pageIndex, int pageSize)
		{
			if (pageIndex < 1)
				pageIndex = 1;

			int skip = (pageIndex - 1) * pageSize;
			try
			{
				IEnumerable<T> pageItems = unpagedItems.Skip(skip).Take(pageSize);
				int totalItemCount = unpagedItems.Count();
				return new PagedList<T>(pageItems, pageIndex, pageSize, totalItemCount);
			}
			catch (Exception)
			{
				return new PagedList<T>(new T[] { }, pageIndex, pageSize, 0);
			}
		}

		/// <summary>
		/// Creates a new <see cref="PagedList{T}"/> from the given unpaged item list.
		/// </summary>
		/// <param name="unpagedItems">The unpaged data source</param>
		/// <param name="pageIndex">The 1-based page index</param>
		/// <param name="pageSize">The maximum number of entries per page</param>
		/// <returns>A <see cref="PagedList{T}"/> instance</returns>
		public static PagedList<T> Create(IQueryable<T> unpagedItems, int pageIndex, int pageSize)
		{
			if (pageIndex < 1)
				pageIndex = 1;

			int skip = (pageIndex - 1) * pageSize;
			try
			{
				IQueryable<T> pageItems = unpagedItems.Skip(skip).Take(pageSize);
				int totalItemCount = unpagedItems.Count();
				return new PagedList<T>(pageItems, pageIndex, pageSize, totalItemCount);
			}
			catch (Exception)
			{
				return new PagedList<T>(new T[] { }, pageIndex, pageSize, 0);
			}
		}

		/// <summary>
		/// Creates a new <see cref="PagedList{T}"/> using the given <see cref="IPageResolver{T}"/> instance.
		/// </summary>
		/// <param name="resolver">The page resolver for <typeparamref name="T"/></param>
		/// <param name="pageIndex">The 1-based page index</param>
		/// <param name="pageSize">The maximum number of entries per page</param>
		/// <returns>A <see cref="PagedList{T}"/> instance</returns>
		public static PagedList<T> Create(IPageResolver<T> resolver, int pageIndex, int pageSize)
		{
			return (new PagedList<T>(resolver.GetPageItems(pageIndex, pageSize), pageIndex, pageSize, resolver.TotalItemCount));
		}

		/// <see cref="IEnumerable{T}.GetEnumerator()"/>
		public IEnumerator<T> GetEnumerator()
		{
			return this.items.GetEnumerator();
		}


		/// <see cref="System.Collections.IEnumerable.GetEnumerator()"/>
		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
		{
			return ((System.Collections.IEnumerable)this.items).GetEnumerator();
		}
	}
}
